/* tslint:disable */
/* eslint-disable */
import type { Alova, MethodType, AlovaGenerics, AlovaMethodCreateConfig } from 'alova';
import { Method } from 'alova';
import apiDefinitions from './apiDefinitions';

const cache = Object.create(null);
const createFunctionalProxy = (array: (string | symbol)[], alovaInstance: Alova<AlovaGenerics>, configMap: any) => {
  const apiPathKey = array.join('.') as keyof typeof apiDefinitions;
  if (cache[apiPathKey]) {
    return cache[apiPathKey];
  }
  // create a new proxy instance
  const proxy = new Proxy(function () {}, {
    get(_, property) {
      // record the target property, so that it can get the completed accessing paths
      const newArray = [...array, property];
      // always return a new proxy to continue recording accessing paths.
      return createFunctionalProxy(newArray, alovaInstance, configMap);
    },
    apply(_, __, [config]) {
      const apiItem = apiDefinitions[apiPathKey];
      if (!apiItem) {
        throw new Error(`the api path of \`${apiPathKey}\` is not found`);
      }
      const mergedConfig = {
        ...configMap[apiPathKey],
        ...config
      };
      const [method, url] = apiItem;
      const pathParams = mergedConfig.pathParams;
      const urlReplaced = url!.replace(/\{([^}]+)\}/g, (_, key) => {
        const pathParam = pathParams[key];
        return pathParam;
      });
      delete mergedConfig.pathParams;
      let data = mergedConfig.data;
      if (Object.prototype.toString.call(data) === '[object Object]' && typeof FormData !== 'undefined') {
        let hasBlobData = false;
        const formData = new FormData();
        for (const key in data) {
          if (Array.isArray(data[key])) {
            for (const ele of data[key]) {
              formData.append(key, ele);
              if (ele instanceof Blob) {
                hasBlobData = true;
              }
            }
          } else {
            formData.append(key, data[key]);
            if (data[key] instanceof Blob) {
              hasBlobData = true;
            }
          }
        }
        data = hasBlobData ? formData : data;
      }
      return new Method(method!.toUpperCase() as MethodType, alovaInstance, urlReplaced, mergedConfig, data);
    }
  });
  cache[apiPathKey] = proxy;
  return proxy;
};

export const createApis = (alovaInstance: Alova<AlovaGenerics>, configMap: any) => {
  // Centralized function to create a Method instance for generic requests
  const createGenericMethod = (method: MethodType, url: string, config: any = {}, data?: any) => {
    const { pathParams, ...restConfig } = config;
    let replacedUrl = url;

    // Replace path parameters in the URL
    if (pathParams) {
      replacedUrl = url.replace(/\{([^}]+)\}/g, (_: string, key: string) => {
        const value = pathParams[key];
        if (value === undefined && process.env.NODE_ENV === 'development') {
          console.warn(`[Alova] Path parameter "${key}" is not provided for URL "${url}"`);
        }
        return String(value);
      });
    }

    // The data for POST/PUT is passed as the last argument to the Method constructor
    const requestData = data ?? restConfig.data;
    // remove data from config if it exists
    if ('data' in restConfig) {
      delete restConfig.data;
    }

    return new Method(method, alovaInstance, replacedUrl, restConfig, requestData);
  };

  const Apis = new Proxy({} as Apis, {
    get(_, property) {
      const propStr = property as string;
      const upperCaseProp = propStr.toUpperCase() as MethodType;

      // A list of all supported HTTP methods for generic calls
      const genericHttpMethods: MethodType[] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'REQUEST'];

      if (genericHttpMethods.includes(upperCaseProp)) {
        // Handle Apis.request({ method, url, ... })
        if (upperCaseProp === 'REQUEST') {
          return (config: any) => {
            const { method, url, ...rest } = config;
            return createGenericMethod(method.toUpperCase() as MethodType, url, rest);
          };
        }

        // Methods that typically don't have a request body
        const methodsWithoutBody: MethodType[] = ['GET', 'DELETE', 'HEAD', 'OPTIONS'];

        // Handle Apis.get(url, config), Apis.post(url, data, config), etc.
        return (url: string, ...args: any[]) => {
          if (methodsWithoutBody.includes(upperCaseProp)) {
            // Signature: .get(url, config)
            const config = args[0] || {};
            return createGenericMethod(upperCaseProp, url, config);
          } else {
            // Signature: .post(url, data, config)
            const data = args[0];
            const config = args[1] || {};
            return createGenericMethod(upperCaseProp, url, config, data);
          }
        };
      }

      // Fallback to predefined APIs (e.g., Apis.pet.getPetById)
      return createFunctionalProxy([property], alovaInstance, configMap);
    }
  });
  return Apis;
};

export const mountApis = (Apis: Apis) => {
  // define global variable `Apis`
  (globalThis as any).Apis = Apis;
};

type MethodConfig<T> = AlovaMethodCreateConfig<
  (typeof import('./index'))['alovaInstance'] extends Alova<infer AG> ? AG : any,
  any,
  T
>;
type APISofParameters<Tag extends string, Url extends string> = Tag extends keyof Apis
  ? Url extends keyof Apis[Tag]
    ? Apis[Tag][Url] extends (...args: any) => any
      ? Parameters<Apis[Tag][Url]>
      : any
    : any
  : any;
type MethodsConfigMap = {
  [P in keyof typeof import('./apiDefinitions').default]?: MethodConfig<
    P extends `${infer Tag}.${infer Url}` ? Parameters<NonNullable<APISofParameters<Tag, Url>[0]>['transform']>[0] : any
  >;
};
export const withConfigType = <Config extends MethodsConfigMap>(config: Config) => config;
